/*:
 * @plugindesc (v1.08)NPC跟随队伍，但此NPC不参与战斗
 * @author 小c
 * @version 1.04
 * @date 1/28/2020
 *
 * @param Max NPC Number
 * @text 最大跟随NPC数量
 * @type number
 * @min 1
 * @default 1
 * @desc 跟随NPC最大数量。
 * 若改掉此值，则修改之前的存档很可能失效
 *
 * @help
 * NPC跟随队伍
 * 让NPC跟随在队伍中，不参加战斗
 * 此插件对导入他之前的游戏存档无效。
 * 测试时若需要对老存档生效，请在进入地图后调用控制台
 * 控制台执行：$gamePlayer.followers().initNpcFollowers()
 * 此后插件才会对旧存档生效。
 *
 * 插件指令
 * NPCFollower add [symbol] [characterName] [characterIndex]
 * 添加队伍跟随NPC
 * symbol：跟随NPC的标记名称，用于管理
 * characterName：NPC角色图片名称
 * characterIndex：NPC角色索引坐标
 * 例：NPCFollower add my maxYu 0
 * 若采用VIP八方向插件，index请设置为0
 *
 * NPCFollower remove [symbol]
 * 移除标记名称为symbol的NPC跟随
 * 例：NPCFollower remove my
 *
 * NPCFollower show
 * 显示所有NPC
 *
 * NPCFollower hide
 * 隐藏所有NPC
 *
 * 注意以下情况
 * 添加NPCA -> 全部隐藏 -> 添加NPCB -> 全部显示
 * 第一步NPC只有A，第二步A消失，第三步NPC只有B，第四步A出现
 * 即第四步以后AB都会显示
 * 如果在存在NPC时【更改队列行进：OFF】，NPC也会消失
 * 此时如果希望NPC跟随，请执行插件指令显示所有NPC
 *
 * 更新日志
 * v1.00
 * 插件完成
 *
 * v1.01
 * 修复跟随队列长度大于1时的添加错乱问题
 *
 * v1.02
 * 修复已有NPC队友时执行取消队友跟随 NPC不消失的问题
 * 若更新到此版本，需重新开始游戏
 *
 * v1.03
 * 修复特定情况下会出现的死机问题
 *
 * v1.04
 * 修复隐藏跟随解除后NPC消失的问题
 * 有NPC跟随的存档升级到此版本不会生效
 * 必须读取NPC进队前的存档运行才会正常
 *
 * v1.05
 * NPC进队时会自动跟随至上一人之后	
 *
 * v1.06
 * 优化NPC自动跟随的规则
 *
 * v1.07
 * 修复关闭队列行进后NPC跟随位置错误的问题
 * 添加插件指令隐藏与显示NPC
 *
 * v1.08
 * 修复显示NPC指令NPC位置错误的问题
 */
var Imported = Imported || {};
Imported.CP_NPCFollower = true;

var CP = CP || {};
CP.NPCFollowerManager = CP.NPCFollowerManager || {};
CP.NPCFollowerManager.Params = PluginManager.parameters("CP_NPCFollower");
CP.NPCFollowerManager.MAX_NPC_NUMBER = Number(CP.NPCFollowerManager.Params["Max NPC Number"]) || 0;

//-------------------------------
//  Game_Character
//-------------------------------
Game_Character.prototype.isFollowerNpc = function(){
	return false;
};

//-------------------------------
//  Game_NPCFollower
//-------------------------------
function Game_NPCFollower(){
	this.initialize.apply(this, arguments);
};

Game_NPCFollower.prototype = Object.create(Game_Follower.prototype);
Game_NPCFollower.prototype.constructor =Game_NPCFollower;

Game_NPCFollower.prototype.initialize = function(index){
	Game_Follower.prototype.initialize.call(this, 0);

	this._npcIndex = index;

	this.initImage();
	this.resetSymbol();
	this.hide();
};

Game_NPCFollower.prototype.isFollowerNpc = function(){
	return true;
};

Game_NPCFollower.prototype.setSymbolName = function(symbolName){
	this._symbolName = symbolName;
};

Game_NPCFollower.prototype.resetSymbol = function(){
	this.setSymbolName("");
};

Game_NPCFollower.prototype.symbolName = function(){
	if(!this._symbolName)
		this.resetSymbol();

	return this._symbolName;
};

Game_NPCFollower.prototype.setupImage = function(characterName, characterIndex){
	if(characterName && characterName.trim() !== ""){
		this._realName = characterName;
		this._realIndex = characterIndex;
	}
};

Game_NPCFollower.prototype.setImage = function(characterName, characterIndex){
	if(this._realName !== characterName || this._realIndex !== characterIndex)
		this.setupImage(characterName, characterIndex);

	Game_Follower.prototype.setImage.call(this, characterName, characterIndex);
};

Game_NPCFollower.prototype.resetImage = function(){
	this.setImage(this._realName, this._realIndex);
};

Game_NPCFollower.prototype.initImage = function(){
	this._realName = "";
	this._realIndex = 0;
};

Game_NPCFollower.prototype.show = function(){
	this._visible = true;
};

Game_NPCFollower.prototype.hide = function(){
	this._visible = false;
}

Game_NPCFollower.prototype.refresh = function() {
	var name = this.isVisible() ? this._characterName : "";
	var index = this.isVisible() ? this._characterIndex : 0;
	Game_Follower.prototype.refresh.call(this);
    this.setImage(name, index);
};

Game_NPCFollower.prototype.isVisible = function(){
	if(!this._visible || this._characterName == "" || this.symbolName() == "")
		return false;
	return true;
};

Game_NPCFollower.prototype.canPass = function(x, y, d){
	if(!Game_Follower.prototype.canPass.call(this, x, y, d))
		return false;
	return !this.hasCharacterInMap(x, y, d);
};
	
Game_NPCFollower.prototype.hasCharacterInMap = function(x, y, d){
	var x1 = $gameMap.roundXWithDirection(x, d);
	var y1 = $gameMap.roundYWithDirection(y, d);

	return this.isCollidedWithCharacters(x1, y1);
};

Game_NPCFollower.prototype.chaseCharacter = function(character){
	var sx = this.deltaXFrom(character.x);
    var sy = this.deltaYFrom(character.y);
    if(Math.abs(sx) === 1 && Math.abs(sy) === 1){
    	this.moveTowardCharacter(character);
    	this.setMoveSpeed($gamePlayer.realMoveSpeed());
    }
    else{
    	Game_Follower.prototype.chaseCharacter.call(this, character);
		if(!this.isMovementSucceeded()){
			this.moveTowardCharacter(character);
		}
    }
};

Game_NPCFollower.prototype.moveTowardCharacter = function(character) {
    var sx = this.deltaXFrom(character.x);
    var sy = this.deltaYFrom(character.y);
    var randomDir = 0;

    Game_Follower.prototype.moveTowardCharacter.call(this, character);
    if(!this.isMovementSucceeded()){
    	if(sx === 0){
    		randomDir = Math.random() < 0.5 ? 4 : 6;
    		if(this.canPass(this.x, this.y, randomDir))
    			this.moveStraight(randomDir);
    		else if(this.canPass(this.x, this.y, 10 - randomDir))
    			this.moveStraight(10 - randomDir);
    	}else if(sy === 0){
    		randomDir = Math.random() < 0.5 ? 2 : 8;
    		if(this.canPass(this.x, this.y, randomDir))
    			this.moveStraight(randomDir);
    		else if(this.canPass(this.x, this.y, 10 - randomDir))
    			this.moveStraight(10 - randomDir);
    	}
    }
};

//-------------------------------
//  Game_Followers
//-------------------------------
CP.NPCFollowerManager.INIT_FOLLOWERS = Game_Followers.prototype.initialize;
Game_Followers.prototype.initialize = function() {
    CP.NPCFollowerManager.INIT_FOLLOWERS.call(this);

    this.initNpcFollowers();
};

Game_Followers.prototype.initNpcFollowers = function(){
	for(var i = 0; i < CP.NPCFollowerManager.MAX_NPC_NUMBER; i++){
		this._data.push(new Game_NPCFollower(i));
	}
};

Game_Followers.prototype.addNpc = function(symbol, characterName, characterIndex){
    if(this.isNpcExisted(symbol))
    	return;

    var index = this.getEmptyNpcIndex();

    if(index >= 0){
    	var follower = this._data[index];
    	follower.setImage(characterName, characterIndex);
    	follower.setSymbolName(symbol);
    	follower.show();
    }

    $gamePlayer.refresh();
    $gameMap.requestRefresh();
};

Game_Followers.prototype.npcFollowerIndex = function(symbol){
	for(var i = 0; i < this._data.length; i++){
		if(this._data[i].isFollowerNpc() && this._data[i].symbolName() == symbol)
			return i;
	}

	return -1;
};

Game_Followers.prototype.isNpcExisted = function(symbol){
	return this.npcFollowerIndex(symbol) >= 0;
};

Game_Followers.prototype.removeNpc = function(symbol){
	if(this.isNpcExisted(symbol)){
		var index = this.npcFollowerIndex(symbol);
		this._data[index].resetSymbol();
		this._data[index].hide();
	}

	$gamePlayer.refresh();
    $gameMap.requestRefresh();
};

Game_Followers.prototype.getEmptyNpcIndex = function(){
	for(var i = 0; i < this._data.length; i++){
		var follower = this._data[i];
		if(follower.isFollowerNpc() && follower.symbolName() == "")
			return i;
	}

	return -1;
};

CP.NPCFollowerManager.REFRESH_FOLLOWERS = Game_Followers.prototype.refresh;
Game_Followers.prototype.refresh = function(){
    CP.NPCFollowerManager.REFRESH_FOLLOWERS.call(this);
    this.refreshOrder();
};

Game_Followers.prototype.refreshOrder = function(){
	var firstList = new Array();
	var secondList = new Array();
	var lastList = new Array();

	for(var i = 0; i < this._data.length; i++){
		var follower = this._data[i];
		if(follower.isFollowerNpc()){ //NPC
			if(follower.isVisible())
				lastList.push(follower);
			else //NPC空位
				secondList.push(follower);
		}else if(follower._memberIndex < $gameParty.battleMembers().length && this.isVisible()) //战斗队列
			firstList.push(follower);
		else //战斗空位
			secondList.push(follower);
	}

	this._data = firstList.concat(lastList).concat(secondList);
};

CP.NPCFollowerManager.HIDE_FOLLOWERS = Game_Followers.prototype.hide;
Game_Followers.prototype.hide = function() {
    CP.NPCFollowerManager.HIDE_FOLLOWERS.call(this);

    this.hideAllNpcs();
};

Game_Followers.prototype.hideAllNpcs = function(){
	this.forEach(function(follower){
    	if(follower.isFollowerNpc())
    		follower.hide();
    });
};

CP.NPCFollowerManager.SHOW_FOLLOWERS = Game_Followers.prototype.show;
Game_Followers.prototype.show = function() {
	CP.NPCFollowerManager.SHOW_FOLLOWERS.call(this);

	this.showAllNpcs();
};

Game_Followers.prototype.showAllNpcs = function(){
	this.forEach(function(follower){
		if(follower.isFollowerNpc()){
			follower.resetImage();
			follower.show();
		}
	});
	this.refreshOrder();
};

CP.NPCFollowerManager.FOLLOWERS_UPDATE = Game_Followers.prototype.update;
Game_Followers.prototype.update = function(){
	CP.NPCFollowerManager.FOLLOWERS_UPDATE.call(this);
	this.updateNpcPosition();
};

Game_Followers.prototype.updateNpcPosition = function(){
	if(this.isUpdateNpcPositionReady())
		this.updateNpcDirection();
	else
		this.updateNpcPositionChange();
    
};

Game_Followers.prototype.updateNpcPositionChange = function(){
	for(var i = this._data.length - 1; i >= 0; i--){
        var precedingCharacter = (i > 0 ? this._data[i - 1] : $gamePlayer);
        if(this.meetsChaseCondition(this._data[i], precedingCharacter)){
        	if(Imported.SAN_AnalogMove)
        		this._data[i].chaseCharacterAnalog(precedingCharacter);
        	else
        		this._data[i].chaseCharacter(precedingCharacter);
        }
    }
};

Game_Followers.prototype.updateNpcDirection = function(){
	for(var i = this._data.length - 1; i >= 0; i--){
		if(!this._data[i].isFollowerNpc() || (!this._data[i].isVisible()))
			continue;
        var precedingCharacter = (i > 0 ? this._data[i - 1] : $gamePlayer);
        var direction = this._data[i].findDirectionTo(precedingCharacter.x, precedingCharacter.y);
        if(this._data[i].direction() !== direction)
        	this._data[i].setDirection(direction);
    }
};

Game_Followers.prototype.meetsChaseCondition = function(character, precedingCharacter){
	if(!character.isFollowerNpc() || !character.isVisible())
		return false;

	if(Imported.SAN_AnalogMove)
		return $gameMap.kingDistance(character.x, character.y, precedingCharacter.x, precedingCharacter.y) > 1;
	else
		return $gameMap.distance(character.x, character.y, precedingCharacter.x, precedingCharacter.y) > 1;
};

Game_Followers.prototype.isUpdateNpcPositionReady = function(){
	for(var i = this._data.length - 1; i >= 0; i--){
        var precedingCharacter = (i > 0 ? this._data[i - 1] : $gamePlayer);
        if(this._data[i].isMoving() || this.meetsChaseCondition(this._data[i], precedingCharacter))
        	return false;
    }
    return true;
};

//-------------------------------
//  Game_Map
//-------------------------------
Game_Map.prototype.kingDistance = function(x1, y1, x2, y2){
	return Math.max(Math.abs(this.deltaX(x1, x2)), Math.abs(this.deltaY(y1, y2)));
};

//-------------------------------
//  Game_Interpreter
//-------------------------------
CP.NPCFollowerManager.PLUGIN_COMMAND = Game_Interpreter.prototype.pluginCommand;
Game_Interpreter.prototype.pluginCommand = function(command, args){
	CP.NPCFollowerManager.PLUGIN_COMMAND.call(this, command, args);

	if(command == 'NPCFollower'){
		var type = args[0].toUpperCase();
		var symbol = args[1];

		switch(type){
			case "ADD":
				var name = args[2];
				var index = Number(args[3]) || 0;
				$gamePlayer.followers().addNpc(symbol, name, index);
				break;
			case "REMOVE":
				$gamePlayer.followers().removeNpc(symbol);
				break;
			case "HIDE":
				$gamePlayer.followers().hideAllNpcs();
				break;
			case "SHOW":
				$gamePlayer.followers().showAllNpcs();
		}
	}
};

//-------------------------------
//  Sprite_Character
//-------------------------------
CP.NPCFollowerManager.UPDATE_CHARACTER_VISIBILITY = Sprite_Character.prototype.updateVisibility;
Sprite_Character.prototype.updateVisibility = function() {
    CP.NPCFollowerManager.UPDATE_CHARACTER_VISIBILITY.call(this);
    if(this._character.isFollowerNpc() && !this._character.isVisible()){
        this.visible = false;
    }
};